
################################################################################
# Paths and configuration
################################################################################

#
# Work out where this file is, so we can find other makefiles in the
# same directory.
#
# If FW_BASE wasn't set previously, work out what it should be
# and set it here now.
#
MK_DIR ?= $(dir $(lastword $(MAKEFILE_LIST)))
ifeq ($(FW_BASE),)
export FW_BASE := $(abspath $(MK_DIR)/..)
endif
include $(MK_DIR)/setup.mk

#
# Set a default target so that included makefiles or errors here don't 
# cause confusion.
#
# XXX We could do something cute here with $(DEFAULT_GOAL) if it's not one
#     of the maintenance targets and set CONFIG based on it.
#
all: firmware

#
# Locate the configuration file
#
ifeq ($(CONFIG),)
$(error Missing configuration name or file (specify with CONFIG=<config>))
endif
$(info % CONFIG = $(CONFIG))
CONFIG_FILE := $(wildcard $(FW_MK_DIR)/config_$(CONFIG).mk)
export CONFIG
include $(CONFIG_FILE)

#
# Sanity-check the BOARD variable and then get the board config.
# If BOARD was not set by the configuration, extract it automatically.
#
# The board config in turn will fetch the toolchain configuration.
#
ifeq ($(BOARD),)
BOARD := $(firstword $(subst _, ,$(CONFIG)))
endif
BOARD_FILE := $(wildcard $(FW_MK_DIR)/board_$(BOARD).mk)
ifeq ($(BOARD_FILE),)
$(error Config $(CONFIG) references board $(BOARD), but no board definition file found)
endif
$(info % BOARD = $(BOARD))
export BOARD
export BOARD_FILE
include $(BOARD_FILE)

#
# If WORK_DIR is not set, create a 'build' directory next to the
# parent Makefile.
#
PARENT_MAKEFILE := $(lastword $(filter-out $(lastword $(MAKEFILE_LIST)),$(MAKEFILE_LIST)))
ifeq ($(WORK_DIR),)
export WORK_DIR := $(dir $(PARENT_MAKEFILE))/build
endif
$(info % WORK_DIR = $(WORK_DIR))

#
# Append the per-board driver directory to the header search path.
#
INCLUDE_DIRS += $(FW_MODULE_SRC)/drivers/boards/$(BOARD)

################################################################################
# NuttX libraries and paths
################################################################################

include $(FW_MK_DIR)/nuttx.mk

################################################################################
# Modules
################################################################################

# where to look for modules
MODULE_SEARCH_DIRS	+= $(WORK_DIR) $(MODULE_SRC) $(FW_MODULE_SRC)

# sort and unique the modules list
MODULES := $(sort $(MODULES))

# locate the first instance of a module by full path or by looking on the
# module search path
define MODULE_SEARCH
	$(firstword $(abspath $(wildcard $(1)/module.mk)) \
		$(abspath $(foreach search_dir,$(MODULE_SEARCH_DIRS),$(wildcard $(search_dir)/$(1)/module.mk))) \
		MISSING_$1)
endef

# make a list of module makefiles and check that we found them all
MODULE_MKFILES		:= $(foreach module,$(MODULES),$(call MODULE_SEARCH,$(module)))
MISSING_MODULES	:= $(subst MISSING_,,$(filter MISSING_%,$(MODULE_MKFILES)))
ifneq ($(MISSING_MODULES),)
$(error Can't find module(s): $(MISSING_MODULES))
endif

# Make a list of the object files we expect to build from modules
# Note that this path will typically contain a double-slash at the WORK_DIR boundary; this must be
# preserved as it is used below to get the absolute path for the module.mk file correct.
#
MODULE_OBJS		:= $(foreach path,$(dir $(MODULE_MKFILES)),$(WORK_DIR)$(path)module.pre.o)

# rules to build module objects
.PHONY: $(MODULE_OBJS)
$(MODULE_OBJS): relpath = $(patsubst $(WORK_DIR)/%,/%,$@)
$(MODULE_OBJS): mkfile = $(patsubst %/module.pre.o,%/module.mk,$(relpath))
$(MODULE_OBJS): workdir = $(@D)
$(MODULE_OBJS): $(GLOBAL_DEPS) $(NUTTX_CONFIG_HEADER)
	$(MKDIR) -p $(workdir)
	$(MAKE) -r -f $(FW_MK_DIR)/module.mk \
		-C $(workdir) \
		MODULE_WORK_DIR=$(workdir) \
		MODULE_OBJ=$@ \
		MODULE_MK=$(mkfile) \
		MODULE_NAME=$(lastword $(subst /, ,$(workdir))) \
		module

# make a list of phony clean targets for modules
MODULE_CLEANS	:= $(foreach path,$(dir $(MODULE_MKFILES)),$(WORK_DIR)/$(path)/clean)

# rules to clean modules
.PHONY: $(MODULE_CLEANS)
$(MODULE_CLEANS): relpath = $(patsubst $(WORK_DIR)%,%,$@)
$(MODULE_CLEANS): mkfile = $(patsubst %/clean,%/module.mk,$(relpath))
$(MODULE_CLEANS):
	@$(ECHO) %% cleaning using $(mkfile)
	$(MAKE) -r -f $(FW_MK_DIR)/module.mk \
	MODULE_WORK_DIR=$(dir $@) \
	MODULE_MK=$(mkfile) \
	clean

################################################################################
# Builtin command list generation
################################################################################

#
# Builtin commands can be generated by the configuration, in which case they
# must refer to commands that already exist, or indirectly generated by modules
# when they are built.
#
# The configuration supplies builtin command information in the BUILTIN_COMMANDS
# variable. Applications make empty files in $(WORK_DIR)/builtin_commands whose
# filename contains the same information.
#
# In each case, the command information consists of four fields separated with a
# period. These fields are the command's name, its thread priority, its stack size
# and the name of the function to call when starting the thread.
#
BUILTIN_CSRC		= $(WORK_DIR)/builtin_commands.c

# command definitions from modules (may be empty at Makefile parsing time...)
MODULE_COMMANDS	= $(subst COMMAND.,,$(notdir $(wildcard $(WORK_DIR)/builtin_commands/COMMAND.*)))

# (BUILTIN_PROTO,<cmdspec>,<outputfile>)
define BUILTIN_PROTO
	$(ECHO) 'extern int $(word 4,$1)(int argc, char *argv[]);' >> $2;
endef

# (BUILTIN_DEF,<cmdspec>,<outputfile>)
define BUILTIN_DEF
	$(ECHO) '    {"$(word 1,$1)", $(word 2,$1), $(word 3,$1), $(word 4,$1)},' >> $2;
endef

# Don't generate until modules have updated their command files
$(BUILTIN_CSRC): $(GLOBAL_DEPS) $(MODULE_OBJS) $(MODULE_MKFILES) $(BUILTIN_COMMAND_FILES)
	@$(ECHO) ========== Generating builtin command list
	@$(ECHO) "CMDS:    $@"
	$(ECHO) '/* builtin command list - automatically generated, do not edit */' > $@
	$(ECHO) '#include <nuttx/config.h>' >> $@
	$(ECHO) '#include <nuttx/binfmt/builtin.h>' >> $@
	$(foreach spec,$(BUILTIN_COMMANDS),$(call BUILTIN_PROTO,$(subst ., ,$(spec)),$@))
	$(foreach spec,$(MODULE_COMMANDS),$(call BUILTIN_PROTO,$(subst ., ,$(spec)),$@))
	$(ECHO) 'const struct builtin_s g_builtins[] = {' >> $@
	$(foreach spec,$(BUILTIN_COMMANDS),$(call BUILTIN_DEF,$(subst ., ,$(spec)),$@))
	$(foreach spec,$(MODULE_COMMANDS),$(call BUILTIN_DEF,$(subst ., ,$(spec)),$@))
	$(ECHO) '    {NULL, 0, 0, NULL}' >> $@
	$(ECHO) '};' >> $@
	$(ECHO) 'const int g_builtin_count = $(words $(BUILTIN_COMMANDS) $(MODULE_COMMANDS));' >> $@

SRCS			+= $(BUILTIN_CSRC)

##EXTRA_CLEANS	+= $(BUILTIN_CSRC)

################################################################################
# Default SRCS generation
################################################################################

#
# If there are no SRCS, the build will fail; in that case, generate an empty
# source file.
#
ifeq ($(SRCS),)
EMPTY_SRC = $(WORK_DIR)/empty.c
$(EMPTY_SRC):
	$(ECHO) '/* this is an empty file */' > $@

SRCS += $(EMPTY_SRC)
endif

################################################################################
# Build rules
################################################################################

#
# What we're going to build.
#
PRODUCT_HEX		= $(WORK_DIR)/firmware.hex
PRODUCT_BIN		= $(WORK_DIR)/firmware.bin
PRODUCT_ELF		= $(WORK_DIR)/firmware.elf

.PHONY: firmware
firmware: $(PRODUCT_HEX) $(PRODUCT_BIN)

#
# Object files we will generate from sources
#
OBJS := $(foreach src,$(SRCS),$(WORK_DIR)/$(src).o)

#
# SRCS -> OBJS rules
#

$(OBJS): $(GLOBAL_DEPS)

$(filter %.c.o,$(OBJS)): $(WORK_DIR)/%.c.o: %.c $(GLOBAL_DEPS)
	$(call COMPILE,$<,$@)

$(filter %.cpp.o,$(OBJS)): $(WORK_DIR)/%.cpp.o: %.cpp $(GLOBAL_DEPS)
	$(call COMPILEXX,$<,$@)

$(filter %.S.o,$(OBJS)): $(WORK_DIR)/%.S.o: %.S $(GLOBAL_DEPS)
	$(call ASSEMBLE,$<,$@)

#
# Built product rules
#

$(PRODUCT_HEX): $(PRODUCT_ELF)
	@$(ECHO) ========== Generating $@
	$(call SYM_TO_HEX,$<,$@)

$(PRODUCT_BIN): $(PRODUCT_ELF)
	@$(ECHO) ========== Generating $@
	$(call SYM_TO_BIN,$<,$@)

$(PRODUCT_ELF): $(OBJS) $(MODULE_OBJS) $(LIBRARY_LIBS) $(GLOBAL_DEPS) $(LINK_DEPS) $(MODULE_MKFILES)
	@$(ECHO) ========== Linking $@
	$(call LINK,$@,$(OBJS) $(MODULE_OBJS) $(LIBRARY_LIBS))

.PHONY: clean
clean: $(MODULE_CLEANS)
	@$(ECHO) %% cleaning
	$(REMOVE) $(PRODUCT_HEX) $(PRODUCT_BIN) $(PRODUCT_ELF)
	$(REMOVE) $(OBJS) $(DEP_INCLUDES) $(EXTRA_CLEANS)
	$(RMDIR) $(NUTTX_EXPORT_DIR)
